import java.util.ArrayList;

import javax.media.opengl.GL;

import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PImage;
import processing.core.PVector;
import processing.opengl.PGraphicsOpenGL;
import remixlab.proscene.Scene;
import SimpleOpenNI.SimpleOpenNI;
import codeanticode.glgraphics.GLConstants;
import codeanticode.glgraphics.GLGraphics;
import codeanticode.glgraphics.GLGraphicsOffScreen;
import codeanticode.glgraphics.GLTexture;
import deadpixel.keystone.CornerPinSurface;
import deadpixel.keystone.Keystone;
import edu.maping.easy.painter.ThingPainterDelegate;

public class OpenNiWrapperFlockingMultiUser extends OpenNiEMWrapper {

	int num = 1150;
	Particle[] particle = new Particle[num];
	PVector noUserCenter = new PVector();
	public static float RATIO = 1F;

	PImage glowKernel;

	GL gl;

	GLGraphicsOffScreen renderer;

	Keystone keystone;
	CornerPinSurface pinSurface;

	public void setup() {
		size((int) (1024 * RATIO), (int) (768 * RATIO), GLConstants.GLGRAPHICS); // strange,
		hint(ENABLE_OPENGL_4X_SMOOTH);
		hint(ENABLE_ACCURATE_TEXTURES);
		hint(ENABLE_NATIVE_FONTS);

		renderer = new GLGraphicsOffScreen(this, width, height, true, 4);// ;

		keystone = new Keystone(this);
		pinSurface = keystone.createCornerPinSurface(width, height, 20);

		super.setup();
		for (int i = 0; i < particle.length; i++) {
			particle[i] = new Particle(new PVector(random(0, width), random(0,
					height)), 4, 20, 5, this.g);
		}

		noUserCenter.x = width / 2;
		noUserCenter.y = height / 2;

		float glowKernelWidth = 40f;// glow core size parameter
		float glowKernelDecayPower = 2f;// glow core decay power
		int reqGlowKernelSize = 3 * (int) pow(100f * glowKernelWidth,
				1f / glowKernelDecayPower);// required image size to accomodate
											// the glow kernel
		glowKernel = createImage(reqGlowKernelSize, reqGlowKernelSize, RGB);
		for (int i = 0; i < reqGlowKernelSize; i++) {
			for (int j = 0; j < reqGlowKernelSize; j++) {
				float r = dist(i, j, reqGlowKernelSize / 2,
						reqGlowKernelSize / 2);
				// if(r<4) r=8-r;
				float bri = 255.999999f * glowKernelWidth
						/ (glowKernelWidth + pow(r, glowKernelDecayPower));// radial
																			// symmetric
																			// decreasing
																			// function
				glowKernel.pixels[i + reqGlowKernelSize * j] = color(bri, bri);
			}
		}
	}

	public void draw() {
		// update the cam

		background(0, 150);

		super.update();

		// run all the particles in the array every frame

		if (calibrating)
			return;

		PVector mouse = pinSurface.getTransformedMouse();

		renderer.beginDraw();
		renderer.background(0);
		renderer.beginGL();
		// We get the gl object contained in the GLGraphics renderer.
		GL gl = renderer.gl;
		gl.glDepthMask(false);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
		
		if (mousePressed) {
			noUserCenter.x = mouse.x * 2f;
			noUserCenter.y = mouse.y * 2f;
		}

		renderer.ellipse(noUserCenter.x, noUserCenter.y, 10, 10);

		if (users != null && !users.isEmpty()) {

			int length = particle.length / users.size();

			for (int j = 0; j < users.size(); j++) {

				int initParticle = length * j;
				int finalPArticle = (length) * (j + 1);

				PVector user = users.get(j);

				for (int i = initParticle; i < finalPArticle; i++) {
					particle[i].run(user.x, user.y); // run() method

					// takes two
					// arguments
					// - x and y values to apply
					// forces to
				}
			}
		} else {
			for (int i = 0; i < particle.length; i++) {
				particle[i].run(noUserCenter.x, noUserCenter.y); // run() method
				// takes two
				// arguments
				// - x and y values to apply
				// forces to
			}
		}

		renderer.endGL();

		renderer.endDraw();

		// if (!calibrating)
		// image(renderer.getTexture(), 0, 0);

		GLTexture glTexture = renderer.getTexture();
		pinSurface.render(glTexture);

		text(frameRate, 10, 10);

	}

	static public void main(String args[]) {
		PApplet.main(new String[] { "--bgcolor=#F0F0F0",
				"OpenNiWrapperFlockingMultiUser" });
	}

	class Particle {
		/*
		 * PVector is a class in Processing that makes it easier to store
		 * values, and make calculations based on these values. It can make
		 * applying forces to objects much easier and more efficient!
		 */
		PVector loc; // location vector
		PVector vel; // velocity vector
		PVector acc; // acceleration vector
		int sz; // size of particle
		float gravity; // gravity variable
		float mass; // mass variable
		int velocityLimit = 5; // the maximum velocity a particle can travel at
		float d; // distance variable between particle and the target
					// co-ordinates

		PGraphics pGraphics;

		// CONSTRUCTOR
		Particle(PVector _loc, int _sz, float _gravity, float _mass,
				PGraphics pg) {
			loc = _loc.get(); // when calling loc, return current location of
								// the selected particle
			vel = new PVector(0, 0); // set vel and acc vectors to 0 as default
			acc = new PVector(0, 0);
			sz = _sz;
			gravity = _gravity;
			mass = _mass;
			this.pGraphics = pg;
		}

		// method to render the particle. control how it lookeystone here!
		public void display(float a) {
			// pGraphics.ellipseMode(CENTER);
			// pGraphics.fill(d, 200, 255);
			// pGraphics.ellipse(loc.x, loc.y, sz, sz);

			renderer.pushMatrix();
			renderer.translate(loc.x, loc.y, loc.z);
			renderer.scale(a * sz * 0.001f);
			renderer.translate(-glowKernel.width / 2, -glowKernel.height / 2);
			renderer.tint(255 - 3 * a + 10 * vel.mag(),
					255 - 3 * a - 15 * vel.mag(), 255 - 25 * vel.mag(), 5 * a);
			renderer.image(glowKernel, 0, 0);
			renderer.popMatrix();
		}

		// math for attraction and repulsion forces
		// tx and ty are the co-ordinates attraction/repulsion will be applied
		// to
		public void forces(float tx, float ty) {
			PVector targetLoc = new PVector(tx, ty); // creating new vector for
														// attractive/repulsive
														// x and y values
			PVector dir = PVector.sub(loc, targetLoc); // calculate the
														// direction between a
														// particle and
														// targetLoc
			d = dir.mag(); // calculate how far away the particle is from
							// targetLoc
			dir.normalize(); // convert the measurement to a unit vector

			// calculate the strength of the force by factoring in a
			// gravitational constant and the mass of a particle
			// multiply by distance^2
			// float force = (gravity * mass) / (d * d);

			// if the mouse is pressed, turn on repulsion by multiplying
			// direction by 1
			if (mousePressed) {
				dir.mult(1);
			}
			// else multiply the direction by -1 to switch the direction the
			// particle travels in (attraction)
			else {
				dir.mult(-1);
			}

			// apply directional vector
			applyForce(dir);
		}

		// method to apply a force vector to the particle
		public void applyForce(PVector force) {
			force.div(mass);
			acc.add(force);
		}

		// method to update the location of the particle, and keep its velocity
		// within a set limit
		public void update() {
			vel.add(acc);
			vel.limit(velocityLimit);
			loc.add(vel);
			acc.mult(0);
		}

		// method to bounce particles of canvas edges
		public void bounds() {
			if (loc.y > height || loc.y < 0) {
				vel.y *= -1;
			}
			if (loc.x > width || loc.x < 0) {
				vel.x *= -1;
			}
		}

		// main method that combines all previous methods, and takes two
		// arguments
		// tx and ty are inherited from forces(), and set the
		// attractive/repulsive co-ords
		public void run(float tx, float ty) {
			forces(tx, ty);
			display(50);
			bounds();
			update();
		}
	}

	@Override
	protected void setParameters(String name, float value) {

	}

	@Override
	public String getName() {
		return "flockingKinect";
	}

	@Override
	public ThingPainterDelegate createNewInstance() {
		return this;
	}

}
